//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2003-2006
// Lowrance and MapCreate are registered trademarks or trademarks of ??
//
// Lowrance/Eagle MapCreate AT5 parser.
//

# include "StdAfx.h"
# include "LowranceAt5.h"
# include "Map.h"
# include "Load.h"
# include "Log.h"
# include "Status.h"
# include "ParseHelpers.h"
# include "fast_floor.h"
# include "Wrappers.h"
# include "Globals.h"

class CLowranceAt5Loader : public CMapLoader {
  public:
	// Override.
	virtual bool IsValid (const char * _pData, size_t _cFileSize) const;
	virtual LoadStatus_t Load (const char * _pData, size_t _cFileSize, const char * _strFileName, map_t * _pMap);

	LoadStatus_t _Load (const BYTE * _pData, size_t _cFileSize, const char * _strFileName, map_t * _pMap, bool _b150);

  private:
	static void  _ReadString (const BYTE * & _p, string_t & _str);
	static WORD  _ReadWORD   (const BYTE * & _p);
	static DWORD _ReadVarLen (const BYTE * & _p);

	rgn_t & AddPoint (size_t _cLevel, USHORT _usType, float _x, float _y, const string_t & _strLabel, size_t _cBeginPos, size_t _cSize);
	rgn_t & AddPoly  (size_t _cLevel, USHORT _usType, RGN_KIND _Kind, const points_t & _points, const string_t & _strLabel, bool _bDirIndicator, size_t _cBeginPos, size_t _cSize);

	map_t * m_pMap;
	const BYTE * m_pStart, * m_pEnd;

	// The counter of objects.
//	size_t m_cOrdinal;

	file_refs_t m_file_refs;
};

//DECLARE_MAP_LOADER (CLowranceAt5Loader, "lowrance-at5", "at5;lcm", "Lowrance MapCreate map (*.at5;*.lcm)");

bool CLowranceAt5Loader::IsValid (const char * _pData, size_t _cFileSize) const {
	if (_cFileSize < sizeof (CLcmHeader))
		return false;

	const CLcmHeader * const pHdr = reinterpret_cast<const CLcmHeader *> (_pData);
	const DWORD dwFormatVer = pHdr->dwFormatVersion;
	// Check the signature.
	return pHdr->dwSignature == c_dwAt5Signature && (dwFormatVer == 4 || dwFormatVer == 6);
}

rgn_t & CLowranceAt5Loader::AddPoint (
	size_t _cLevel, USHORT _usType, float _x, float _y, const string_t & _strLabel,
	size_t _cBeginPos, size_t _cSize
) {
	rgn_t & rgn = m_pMap->CreateRgn (rkPoint);

	// Initialize the rgn.
	rgn.cBeginPos = _cBeginPos;
	rgn.cEndPos   = _cBeginPos + _cSize;
	rgn.cFileLine = -1;
//	rgn.cOrdinal  = m_cOrdinal ++;
	rgn.pFileRef  = & m_file_refs.back ();

	rgn.SetType (m_pMap->pTypeSet, _usType, rkPoint);

	if (! _strLabel.empty ())
		rgn.strLabel = _strLabel;

	rgn.origins.resize (1);
	rgn_origin_t & origin = rgn.origins [0];
	origin.cFileOffset = _cBeginPos;
	origin.cLevel = _cLevel;
	origin.point.x = _x;
	origin.point.y = _y;

	return rgn;
}

rgn_t & CLowranceAt5Loader::AddPoly (
	size_t _cLevel, USHORT _usType, RGN_KIND _Kind, const points_t & _points, const string_t & _strLabel, bool _bDirIndicator,
	size_t _cBeginPos, size_t _cSize
) {
	rgn_t & rgn = m_pMap->CreateRgn (_Kind);

	// Initialize the rgn.
	rgn.cBeginPos = _cBeginPos;
	rgn.cEndPos   = _cBeginPos + _cSize;
	rgn.cFileLine = -1;
//	rgn.cOrdinal  = m_cOrdinal ++;
	rgn.pFileRef  = & m_file_refs.back ();

	rgn.SetType (m_pMap->pTypeSet, _usType, _Kind);

	if (! _strLabel.empty ())
		rgn.strLabel = _strLabel;

	if (_bDirIndicator)
		rgn.dwFlags |= rgn_t::fDirIndicator;

	rgn.elements.resize (1);
	rgn_element_t & element = rgn.elements [0];
	element.cFileOffset = _cBeginPos;
	element.cLevel = _cLevel;
	element.points = _points;
	element.bHole = false;

	return rgn;
}

void CLowranceAt5Loader::_ReadString (const BYTE * & _p, string_t & _str) {
	const BYTE btLen = * _p;
	++ _p;
	const char * const strId = reinterpret_cast<const char *> (_p);
	_p += btLen;

	_str.assign (strId, btLen);
}

WORD CLowranceAt5Loader::_ReadWORD (const BYTE * & _p) {
	const WORD w = * reinterpret_cast<const WORD *> (_p);
	_p += sizeof (WORD);
	return w;
}

DWORD CLowranceAt5Loader::_ReadVarLen (const BYTE * & _p) {
	DWORD dw = * _p;
	++ _p;
	if (dw & 0x1)
		return dw >> 1;

	dw = dw + (DWORD (* _p) << 8);
	++ _p;
	if ((dw >> 1) & 0x1)
		return dw >> 2;

	dw = dw + (DWORD (* _p) << 16);
	++ _p;
	if ((dw >> 2) & 0x1)
		return dw >> 3;

	dw = dw + (DWORD (* _p) << 24);
	return dw >> 4;
}

static
void _Mercator2Degrees (long _lLat, long _lLon, float & _fLat, float & _fLon) {
	_fLat = (2*::atan (::exp (_lLat/6356752.3142)) - c_PI/2)*180/c_PI;
	_fLon = (_lLon/6356752.3142)*180/c_PI;
}

LoadStatus_t CLowranceAt5Loader::Load (const char * _pData, size_t _cFileSize, const char * _strMapFileName, map_t * _pMap) {
	m_pMap   = _pMap;
	m_pStart = reinterpret_cast<const BYTE *> (_pData);
	m_pEnd   = m_pStart + _cFileSize;

	m_file_refs.clear ();
	m_file_refs.push_back (file_ref_t ());
	m_file_refs.back ().strFileName = _strMapFileName;
	m_file_refs.back ().bAttachment = false;

	SetProgressStatus (0);

	//
	// Parse header data.
	//

	const BYTE * p = m_pStart;

	const CAt5Header * const pHdr = reinterpret_cast<const CAt5Header *> (m_pStart);
	ReportText ("* Format version:\t%d", pHdr->dwFormatVersion);

	const bool bAT5 = pHdr->dwFormatVersion == 6;
	const size_t cHdrSize  = bAT5 ? sizeof (CAt5Header)  : sizeof (CLcmHeader);
	const size_t cHdr2Size = bAT5 ? sizeof (CAt5Header2) : sizeof (CLcmHeader2);

	p += cHdrSize;

	// ID.
	string_t strId;
	_ReadString (p, strId);
	ReportText ("* Id:   \t%s", strId.c_str ());

	// Date.
	string_t strDate;
	_ReadString (p, strDate);
	ReportText ("* Date: \t%s", strDate.c_str ());

	// "Blank".
	string_t strBlank;
	_ReadString (p, strBlank);

	const CAt5Header2 * const pHdr2 = reinterpret_cast<const CAt5Header2 *> (p);
	p += cHdr2Size;

	// Map bounds.
	assert (pHdr2->lNorth > pHdr2->lSouth);
	assert (pHdr2->lEast  > pHdr2->lWest);
	float x0, x1, y0, y1;
	_Mercator2Degrees (pHdr2->lSouth, pHdr2->lWest, y0, x0);
	_Mercator2Degrees (pHdr2->lNorth, pHdr2->lEast, y1, x1);

	const BYTE * const pSections = m_pStart + cHdrSize + pHdr->dwDataOffset;
	assert (p == pSections);

	//
	// Section 0.
	//

	const BYTE * const pSection0 = pSections + pHdr2->dwSection00_Offset;
	assert (pHdr2->dwSection00_HdrSize == 0x24);
	assert (pSection0 [0] == 0x0b);
	assert (pSection0 [1] == 0x80);
	assert (pSection0 [2] == 0x02);

	//
	// Section 1 - Types.
	//

	const BYTE * const pTypes = pSections + pHdr2->dwTypes_Offset;
	assert (pHdr2->dwTypes_Offset == pHdr2->dwSection00_Offset + pHdr2->dwSection00_HdrSize);
	// TODO:

	//
	// Section 2 - Icons.
	//

	const BYTE * const pIcons = pSections + pHdr2->dwIcons_Offset;
	// TODO:

	//
	// Section 3 - Primitives.
	//

	const BYTE * const pLevelsHdr = pSections + pHdr2->dwBlocks_Offset - 1;
	const BYTE * const pBlocks = pLevelsHdr + pHdr2->dwBlocks_HdrSize;

	p = pLevelsHdr;
	const DWORD dwLevels = _ReadVarLen (p);
	for (size_t l = 0; l < dwLevels; ++ l) {
		const DWORD dwOffset = _ReadVarLen (p);
		const DWORD dwLength = _ReadVarLen (p);
		assert (dwLength == 66 || dwLength == 107);

		const BYTE * p2 = pBlocks + dwOffset;
		const CAt5Block * const pBlock = reinterpret_cast<const CAt5Block *> (p2);
		p2 += sizeof (CAt5Block);

		const DWORD dwBlocks = _ReadVarLen (p2);
		assert (dwBlocks == 5);

		p2 += dwBlocks*2*sizeof (DWORD);

		const DWORD dwTableDataLen = _ReadVarLen (p2);
		const DWORD dwSomeLen      = _ReadVarLen (p2);

		// Bounding polygon.
		const DWORD dwBoundNodes   = _ReadVarLen (p2);
		assert (dwBoundNodes == 5);
		p2 += dwBoundNodes*2*sizeof (LONG);
		p2 += 2*sizeof (LONG);

		const size_t cRows  = _ReadWORD   (p2);
		const size_t cCols  = _ReadWORD   (p2);
		const DWORD dwLists = _ReadVarLen (p2);
		assert (dwLists == cRows*cCols);
	}

	return lsOK;
}